package monitor

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/aldrice/monitor/configs"
	"gitee.com/aldrice/monitor/ent"
	"gitee.com/aldrice/monitor/ent/metric"
	"gitee.com/aldrice/monitor/ent/rule"
	"gitee.com/aldrice/monitor/ent/setting"
	. "gitee.com/aldrice/monitor/internal/common"
	"gitee.com/aldrice/monitor/internal/registry"
	"github.com/streadway/amqp"
	"strconv"
	"strings"
	"time"
)

// 记录聚合方法、聚合时间、聚合数值
type aggregation struct {
	Method string
	Length int
	Value  float64
}

// DataProcessModule 数据聚合、规则判断模块
type DataProcessModule struct {
}

const measurement = "monitor_backend"

// Behavior 数据聚合、规则判断模块处理方法
func (d *DataProcessModule) Behavior(msg amqp.Delivery) (string, error) {
	ctx := context.Background()

	// 读取消息
	processMsg := ProcessMsg{}
	err := json.Unmarshal(msg.Body, &processMsg)
	if err != nil {
		return "fail to Unmarshal message", err
	}

	// 获取object信息
	obj, err := registry.Ent().Object.Get(ctx, processMsg.Id)
	if err != nil {
		return "fail to get object", err
	}

	// 聚合数据
	aggregates, err := dataAggregate(obj, ctx)
	if err != nil {
		return "failed to aggregate data", err
	}
	fmt.Println("聚合后数据：", aggregates)

	// 根据用户配置规则判断单个指标是否触发告警规则
	maxAlarm, alarmMetrics, err := thresholdJudge(ctx, aggregates, obj)
	if err != nil {
		return "failed to obtain alarm rules", err
	}
	if len(alarmMetrics) == 0 {
		return "", nil
	}

	// 获取告警短信发送详情，并将其写入告警日志
	details, err := alarmDetails(ctx, obj, maxAlarm, aggregates, alarmMetrics, processMsg.Timestamp)
	if err != nil {
		return "failed to write alarm record log", err
	}
	fmt.Println("告警详情：", details)

	// 将信息放入 sendEmail 队列
	sendEmailMQ := registry.GetSendEmailMQ()
	marshal, err := json.Marshal(EmailMsg{Id: obj.ID, Details: details, MaxAlarm: maxAlarm})
	if err != nil {
		return "", nil
	}
	err = sendEmailMQ.PublishSimple(marshal)
	if err != nil {
		return "Failed put data into sendEmailMQ", nil
	}

	return "", nil
}

// 数据聚合
// @return [metric]Aggregate-value
func dataAggregate(obj *ent.Object, ctx context.Context) (map[*ent.Metric]aggregation, error) {

	// 根据ip获取setting
	settings, err := obj.QueryAggregatedBy().All(ctx)
	if err != nil {
		return nil, err
	}
	aggregateMap := make(map[*ent.Metric]aggregation)

	// 聚合数据
	for _, s := range settings {
		// 该setting不生效时直接跳过
		if !s.Enable {
			continue
		}
		// 获取指标
		m, e := s.QueryWithMetric().Only(ctx)
		if e != nil {
			return nil, e
		}

		var query string
		// 数据聚合
		if s.Method == setting.MethodNone {
			query = fmt.Sprintf("from(bucket:\"%v\") "+
				"|> range(start: -%ds) "+
				"|> filter(fn: (r) => r._measurement == \"%v\") "+
				"|> filter(fn: (r) => r._field == \"%v\")"+
				"|> filter(fn: (r) => r.%s == \"%s\") ",
				configs.Config.InfluxDB.Bucket, obj.ReportInterval, measurement, m.Name, machineId, string(rune(obj.ID)))
		} else {
			query = fmt.Sprintf("from(bucket:\"%v\") "+
				"|> range(start: -%dm) "+
				"|> filter(fn: (r) => r._measurement == \"%v\") "+
				"|> filter(fn: (r) => r._field == \"%v\")"+
				"|> filter(fn: (r) => r.%s == \"%s\") "+
				"|> %s() ",
				configs.Config.InfluxDB.Bucket, s.Length, measurement, m.Name, machineId, string(rune(obj.ID)), s.Method)
		}
		queryAPI := *registry.GetQueryAPI()
		data, err := queryAPI.Query(context.Background(), query)
		if err != nil {
			return nil, err
		}
		if data.Next() {
			aggregateMap[m] = aggregation{s.Method.String(), s.Length, data.Record().Value().(float64)}
		} else {
			return nil, errors.New("failed to aggregate data")
		}
	}
	return aggregateMap, nil
}

// 获取全部指标的告警等级 及 最高告警级别
func thresholdJudge(ctx context.Context, aggregates map[*ent.Metric]aggregation, obj *ent.Object) (int, map[*ent.Metric]*ent.Rule, error) {
	// key：指标 value：该指标触发的最高告警规则（不触发告警的指标不会出现在该map中）
	alarmMetrics := make(map[*ent.Metric]*ent.Rule)
	// 本次告警最高告警级别
	var maxAlarm int
	for key, aggregate := range aggregates {
		rules, err := obj.QueryRuledBy().Where(rule.HasInMetricWith(metric.NameEqualFold(key.Name))).All(ctx)
		if err != nil {
			return maxAlarm, nil, err
		}
		if r := metricJudge(rules, aggregate.Value); r != nil {
			alarmMetrics[key] = r
			// 获取本次告警的最高告警级别
			if r.AlarmLevel > maxAlarm {
				maxAlarm = r.AlarmLevel
			}
		}
	}
	return maxAlarm, alarmMetrics, nil
}

//根据指标值聚合后数值，判断该指标最高告警等级
func metricJudge(rules []*ent.Rule, aggregate float64) *ent.Rule {
	var rl *ent.Rule
	for _, r := range rules {
		// 该规则不生效时直接跳过
		if !r.Enable {
			continue
		}
		if (r.MaxAlarm != nil && aggregate >= *r.MaxAlarm) || (r.MinAlarm != nil && aggregate <= *r.MinAlarm) {
			if rl == nil || rl.AlarmLevel < r.AlarmLevel {
				rl = r
			}
		}
	}
	return rl
}

// 告警等级
const (
	levelWarning = 1
	levelPanic   = 2
	levelFatal   = 3
)

func getLevelString(lv int) string {
	switch lv {
	case levelFatal:
		return "fatal"
	case levelPanic:
		return "panic"
	case levelWarning:
		return "warning"
	default:
		return "unknown alarm level"
	}
}

/*
生成告警详情，并将告警记录插入数据库
告警指标： xxx
告警等级： xxx
时间范围： xxx - xxx
聚合方式： xxx
聚合数值： xxx
告警上界： xxx
告警下界： xxx （如果存在）
*/
func alarmDetails(ctx context.Context,
	obj *ent.Object,
	maxAlarm int,
	aggregates map[*ent.Metric]aggregation,
	alarmMetrics map[*ent.Metric]*ent.Rule,
	timestamp int64) ([]AlarmItem, error) {

	var detailsBuilder strings.Builder
	details := make([]AlarmItem, len(alarmMetrics))

	// 开启告警记录写入事务
	db := registry.Ent()
	tx, err := db.Tx(ctx)
	if err != nil {
		return details, err
	}

	i := 0
	for m, r := range alarmMetrics {
		item := AlarmItem{MinLimit: "-inf", MaxLimit: "+inf"}

		// 生成告警详情
		detailsBuilder.WriteString("触发规则： ")
		detailsBuilder.WriteString(r.Nickname)
		item.RuleName = r.Nickname

		aggregate := aggregates[m]
		detailsBuilder.WriteString("\n告警指标： ")
		detailsBuilder.WriteString(m.Name)
		item.Metric = m.Name

		detailsBuilder.WriteString("\n告警等级： ")
		detailsBuilder.WriteString(getLevelString(r.AlarmLevel))
		item.AlarmLevel = r.AlarmLevel

		detailsBuilder.WriteString("\n时间范围： ")
		var timeRange string
		if aggregate.Length != 0 {
			beginStamp := timestamp - 60*int64(aggregate.Length)
			timeRange += time.Unix(beginStamp, 0).Format("2006-01-02 15:04:05")
			timeRange += " - "
		}
		timeRange += time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
		detailsBuilder.WriteString(timeRange)
		item.Time = timeRange

		detailsBuilder.WriteString("\n聚合方式： ")
		detailsBuilder.WriteString(aggregate.Method)
		item.Method = aggregate.Method

		detailsBuilder.WriteString("\n聚合数值： ")
		value := strconv.FormatFloat(aggregate.Value, 'f', 2, 64)
		detailsBuilder.WriteString(value)
		item.Value = value

		if r.MaxAlarm != nil {
			detailsBuilder.WriteString("\n告警上界： ")
			value = strconv.FormatFloat(*r.MaxAlarm, 'f', 2, 64)
			detailsBuilder.WriteString(value)
			item.MaxLimit = value
		}

		if r.MinAlarm != nil {
			detailsBuilder.WriteString("\n告警下界： ")
			value = strconv.FormatFloat(*r.MinAlarm, 'f', 2, 64)
			detailsBuilder.WriteString(value)
			item.MinLimit = value
		}

		details[i] = item
		i++
		// 写入告警日志
		_, e := tx.AlarmLog.Create().
			SetAlarmAt(time.Unix(timestamp, 0)).
			SetRecordLevel(maxAlarm).
			SetMetricLevel(r.AlarmLevel).
			SetDetail(detailsBuilder.String()).
			SetSentForObject(obj).
			SetSentForMetric(m).
			Save(ctx)
		if e != nil {
			err = tx.Rollback()
			return details, err
		}
		detailsBuilder.Reset()
	}
	return details, tx.Commit()
}
